package Controller;
/* 
 This manages the control flow in the entire application. This 
 class manages any data that is received by the server or is 
 sent to the client. The actions that have to be performed when 
 a button is clicked on the GUI are also handled here.
 */ 
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.crypto.KeyGenerator;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileNameExtensionFilter;
import des.Decryptfile;
import des.Encryptfile;
import dsm_ui.*;
import sound.*;
import store.storage;
import comm.*;

public class MainController implements MsgAction
{
	//Server Id
	static private String SERVER_IP = "192.168.0.22";

	private AudioPlayer player;
	private AudioRecorder recorder;
	private PcmMixer mixer;
	private MainGUI GUI;
	private File openFile, saveFile;
	private MessageParser parser;
	private Key encryptKey;
	private String pin;
	private boolean coordinator;
	private List<storage> iptable;
	private String chooserPath;
	private String coordip;
	private List<storage> group; 
	private List<storage> CoordList;
	private storage myip;
	private List<File> tobeMixed;
	private MessageBuilder sender;
	private int maxGroupSize;
	
	public MainController(String serverip)
	{
		
		System.out.println(" Application Started");
		System.out.println("Registering to " + SERVER_IP);
		
		this.sender = new MessageBuilder();
		this.parser = new MessageParser();
		this.parser.addActionProvider(this);
		
		//Register to Server
		if(serverip != null)
		{
			this.myip = this.sender.registerServer(serverip);
		}
		else
		{
			this.myip = this.sender.registerServer(SERVER_IP);
		}
		
		
		this.coordinator = false;
		
		//Build the GUI
		this.GUI = new MainGUI();
		this.GUI.setLocationRelativeTo(null);
		this.GUI.setVisible(true);
		this.GUI.addOpenListener(new OpenListener());
		this.GUI.addPlayListener(new PlayButtonListener());
		this.GUI.addStopListener(new StopButtonListener());
		this.GUI.addRecordListener(new RecordListener());
		this.GUI.addSaveListener(new SaveListener());
		this.GUI.addRemoveListener(new RemoveListener());
		this.GUI.addCoordSelectListener(new ConnectListener());
		this.GUI.addSetCoordListener(new SetCoordListener());
		this.GUI.addWindowCloseListener(new WindowCloseListener());
		this.GUI.addTestMixListener(new TestMixListener());
		
		//Disable buttons
		this.GUI.changeplay_button(false);
		this.GUI.changeRecord(false);
		this.GUI.changeRemove(false);
		this.GUI.changeSave(false);
		this.GUI.changeStop(false);
		this.GUI.changeTestMix(false);
		
	}
	
		
	//Action performed when 'record' button is clicked
	class RecordListener implements ActionListener
	{
		public void actionPerformed(ActionEvent e)
		{
			if(group != null)
			{
				Iterator<storage> iterator = group.iterator();	
				while(iterator.hasNext())
				{
					sender.startRecord(iterator.next().getIp());	//Recording process starts
				}
			}			
			GUI.changeStop(true);
			//recorder = new AudioRecorder();
			//recorder.startRecording();
		}
	}
	
	//Action performed when 'remove' button is clicked
	class RemoveListener implements ActionListener
	{
		public void actionPerformed(ActionEvent e)
		{
			GUI.removeSelectedFile(GUI.getSelectedFile());	//The file selected is removed from the playlist
			
		}
	}
	
	//Action performed when 'TestMix' button is clicked
	class TestMixListener implements ActionListener
	{
		public void actionPerformed(ActionEvent e)
		{
			if(encryptKey == null)
			{
				try {
					encryptKey = KeyGenerator.getInstance("DES").generateKey();
				} catch (NoSuchAlgorithmException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
			
			if(group != null)
			{
				Iterator<storage> iterator = group.iterator();	
				while(iterator.hasNext())
				{
					sender.TestMix(iterator.next().getIp(), encryptKey);	//Recording process starts
				}
			}
			
			
			
			
		}
	}
	
	//Action performed when 'connect' button is clicked
	class ConnectListener implements ActionListener
	{
		//Connect button calls this
		public void actionPerformed(ActionEvent e)
		{
			pin = GUI.getPIN();
			storage coord = (storage) GUI.getSelectedCoord();	//Object stores the IP address of the selected coordinator
			if(coord == null)
			{
				JOptionPane.showMessageDialog(new JEditorPane(), "Please select a Coordinator to Join.");	//Error displayed when no coordinator is selected
			}
			else if(pin.length() == 0)
			{
				JOptionPane.showMessageDialog(new JEditorPane(), "Please enter the PIN provided by the coordinator.");	//Error displayed when no PIN is entered
			}
			else
			{
				//process connect
				coordip = coord.getIp();	//Stores IP address of client
				coord.getGroup();
				sender.reqJoin(pin,coordip);	//Requests for joining group
				System.out.println("Connecting to " + coordip);
				
			}
			
			
		}
	}
	
	//Action performed when 'set as coordinator' button is clicked
	class SetCoordListener implements ActionListener
	{
		public void actionPerformed(ActionEvent e)
		{
			pin = GUI.getPIN();	//Gets the PIN for authentication
			if(pin.length() == 0)
			{
				JOptionPane.showMessageDialog(new JEditorPane(), "Please provide a PIN for the group.");	
			}
			else
			{
				String str = JOptionPane.showInputDialog(new JEditorPane(), "Please Enter Group Size : ", 
															"Group Size", JOptionPane.QUESTION_MESSAGE);
				if(str != null)
				{
					maxGroupSize = Integer.parseInt(str);
					if(group==null)
					{
						group = new ArrayList<storage>();	//Initialize an Arraylist
					}
					coordinator = true;	
					coordip = myip.getId();
					myip.setRole("Coordinator");	//Set the role of client as coordinator
					myip.setGroup(myip.getIp());	//Set the group id of the client
					group.add(myip);				//add IP address to the arraylist
					sender.updateServer(myip, SERVER_IP);	//Update IP table
					
					//Change Button Status
					GUI.changecoord_select(false);
					GUI.changecoordSelect(false);
					GUI.changeRecord(true);
					GUI.changeSetCoord(false);
					GUI.changeTestMix(true);
					
					System.out.println("Set as Coordinator");
				}
				
			}
		}
	}
	
	//Action performed when 'open' sub-menu is selected
	class OpenListener implements ActionListener
	 {
		public void actionPerformed(ActionEvent e)
		{
			//Helps to choose a file from the directories present in the host computer
			JFileChooser chooser = new JFileChooser();
		    if(chooserPath != null)
		    {
		    	chooser.setCurrentDirectory(new File(chooserPath));
		    }
		    chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);	
		    FileNameExtensionFilter filter = new FileNameExtensionFilter("au files", "au");
		    chooser.setFileFilter(filter);
		    int retcode = chooser.showOpenDialog(new JEditorPane());
            if (retcode == JFileChooser.APPROVE_OPTION) 
            {
               openFile = chooser.getSelectedFile();
               chooserPath = openFile.getPath();
               GUI.addFile(openFile);
               GUI.setSelectedFile(openFile);
               GUI.changeplay_button(true);
               GUI.changeplayListCombo(true);
               GUI.changeRemove(true);
            }
		}
	}
	
	//Action performed when 'save' sub-menu is selected
	class SaveListener implements ActionListener
	{
		public void actionPerformed(ActionEvent e)
		{
			if(recorder != null)
			{
				JFileChooser chooser = new JFileChooser();
			    
				if(chooserPath != null)
			    {
			    	chooser.setCurrentDirectory(new File(chooserPath));
			    	//chooser.setSelectedFile(new File(fileName));
			    }
				chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
			    FileNameExtensionFilter filter = new FileNameExtensionFilter("au files", "au");
			    chooser.setFileFilter(filter);
			    int retcode = chooser.showSaveDialog(new JEditorPane());
	            if (retcode == JFileChooser.APPROVE_OPTION) 
	            {
	            	if(chooser.getSelectedFile() != null)
	            	{
	            		saveFile = chooser.getSelectedFile();
	            		recorder.getFile().renameTo(saveFile);
	            	}
	               
	            }
			}
			else
			{
				JOptionPane.showMessageDialog(new JEditorPane(), "Please record a file before saving.");
			}
		}
	}

	//Action performed when 'play' button is pressed
	class PlayButtonListener implements ActionListener
	{
		public void actionPerformed(ActionEvent e)
		{
            openFile = (File) GUI.getSelectedFile();	//Opens the file
			player = new AudioPlayer(openFile);
			player.startPlayback();	 //Plays the audio file
			GUI.changeStop(true);
		}
	}
	
	//Action performed when 'stop' button is clicked
	class StopButtonListener implements ActionListener
	{
		public void actionPerformed(ActionEvent e)
		{
			if(player != null)
			{
				if(player.getPlayerStatus() == true)
				{
					player.stopPlayback();			//Stop playing
				}
			}
			
			if(recorder != null)
			{
				if(recorder.getStatus() == true)
				{			
					if(group != null)
					{
						Iterator<storage> iterator = group.iterator();
						while(iterator.hasNext())
						{
							sender.stopRecord(iterator.next().getIp());	//Stop recording
						}
	
					}
					System.out.println("Stop Recording");
					
					//Stop recording
					//recorder.stopRecording();
					
					//get file for mixing
					mixer = new PcmMixer(recorder.getFile());
					tobeMixed = new ArrayList<File>();
					tobeMixed.add(recorder.getFile());
					
					//Generate Key
					try {
						encryptKey = KeyGenerator.getInstance("DES").generateKey();
					} catch (NoSuchAlgorithmException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
					
					//Send the Key to group members
					if(group != null)
					{
						Iterator<storage> iterator = group.iterator();
						while(iterator.hasNext())
						{
							sender.sendKey(encryptKey,iterator.next().getIp());
						}
	
					}
					
					GUI.changeRemove(true);
					GUI.changeSave(true);
				}
			}
			
		}
	}
	
	//Action performed when client window actions happen
	class WindowCloseListener implements WindowListener
	{

		@Override
		//On closing the window;
		public void windowClosing(WindowEvent arg0) 
		{
			// TODO Auto-generated method stub
			sender.disconnect(SERVER_IP);
			JOptionPane.showMessageDialog(new JEditorPane(), "Closing Application. Disconnecting...");
			System.exit(0);
		}
		
		@Override
		//On activating window;
		public void windowActivated(WindowEvent arg0) 
		{
			// TODO Auto-generated method stub
			
		}

		@Override
		//On closing the window;
		public void windowClosed(WindowEvent arg0) 
		{
			// TODO Auto-generated method stub
			
		}

		
		@Override
		//On deactivating window
		public void windowDeactivated(WindowEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void windowDeiconified(WindowEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void windowIconified(WindowEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		//On opening the window;
		public void windowOpened(WindowEvent arg0) {
			// TODO Auto-generated method stub
			
		}
	}


	@Override
	//Update the client details in the IP table
	public void updateClientTable(List<storage> st, String ip) 
	{
		System.out.println("Update Recived");
		this.iptable = st;
		this.CoordList = new ArrayList<storage>();
		storage temp;
		if(ip.equals(SERVER_IP))
		{
			//initialize iterator
			Iterator<storage> iterator = this.iptable.iterator();
			while(iterator.hasNext())
			{
				////get next value into iterator
				temp = iterator.next();
				System.out.println("Ip : " + temp.getIp() + " Role :" + temp.getRole());
				if(temp.getRole().equals("Coordinator"))
				{
					this.CoordList.add(temp);	//Add the IP addresses to the IP table
				}
			}
			
			this.GUI.setCoordList(this.CoordList);
		}
		
	}

	@Override
	//Accept a join request from the client
	public void joinAccepted(String ip) 
	{
		// TODO Auto-generated method stub
		//display dialog to user that the join request is accepted
		if(ip.equals(this.coordip))	//Message is sent to the client that sent a join request
		{
			JOptionPane.showMessageDialog(new JEditorPane(), "Congrats, You have joined the Group.");
			GUI.changeSetCoord(false);
			GUI.changecoord_select(false);
			GUI.changecoordSelect(false);
			System.out.println("Joined with " + ip);
		}
		
		
		
		
	}

	
	@Override
	//Reject a join request from the client
	public void joinRejected(String ip) 
	{
		// TODO Auto-generated method stub
		//display dialog to user that the PIN is wrong
		JOptionPane.showMessageDialog(new JEditorPane(), "Unable to Join the selected Coordinator. Please Check your PIN. ");
		System.out.println(" Connect Rejected By " + ip);
	}

	
	@Override
	//Send a request to the Server to join the group
	public void requestJoin(String recvPin, String ip) 
	{
		if(this.coordinator == true)
		{
			if(!this.pin.equals(recvPin) || this.maxGroupSize == this.group.size())
			{
				//send request reject message
				this.sender.rejectJoin(ip);
			}
			else	
			{
				storage temp;
				Iterator<storage> iterator = this.iptable.iterator();
				while(iterator.hasNext())
				{
					temp = iterator.next();
					if(temp.getIp().equals(ip))
					{
						temp.setGroup(myip.getIp());	//set the 
						this.group.add(temp);
						//send Acknowledgment
						this.sender.ackJoin(ip);
						//update server entry
						this.sender.updateServer(temp, SERVER_IP);
						
						if(this.maxGroupSize > this.group.size())
						{
							JOptionPane.showMessageDialog(new JEditorPane(), "New Participant Joined. Size of Group : " + this.group.size());
							System.out.println("New Participant Joined : " + ip + " Group Size : " + this.group.size());
						}
						else
						{
							JOptionPane.showMessageDialog(new JEditorPane(), "New Participant Joined. Congrats Group is full.");
							System.out.println("New Participant Joined : " + ip + " Group Size : " + this.group.size());
							System.out.println("Group Full");
						}
						
						
						
					}
				}
			}
		}
		
	}

	
	@Override
	//Start the recording process
	public void startRecord(String ip) 
	{
		if(this.recorder == null)
		{
			this.recorder = new AudioRecorder();
		}
		this.recorder.startRecording();
		JOptionPane.showMessageDialog(new JEditorPane(), "Recording Started....");
		System.out.println("Start Recording");
		
	}
	
	@Override
	//Stop the recording process
	public void stopRecord(String ip)
	{
		this.recorder.stopRecording();
		this.GUI.addFile(this.recorder.getFile());
		this.GUI.setSelectedFile(this.recorder.getFile());
		JOptionPane.showMessageDialog(new JEditorPane(), "Recording Stopped....");
		System.out.println("Stop Recording");
		this.GUI.changeplay_button(true);
			
	}

	@Override
	//Encrypt the audio file using the received key
	public void recieveKey(Key k, String ip) 
	{
		if(ip.equals(this.coordip))
		{
			this.encryptKey = k;
			this.sender.sendFile(Encrypt(recorder.getFile()),this.coordip);
			//this.sender.sendFile(recorder.getFile(),this.coordip);
			System.out.println("Encrypt Key Recieved from " + ip);
		}
	}

	
	@Override
	//
	public void recieveFile(File inFile, String ip) 
	{
		this.GUI.changeSave(true);
		System.out.println("Encrypted File " + inFile.getPath() + " recieved from " + ip);
		
		if(this.mixer == null)
		{
			this.mixer = new PcmMixer();
		}
		if(this.tobeMixed == null)
		{
			tobeMixed = new ArrayList<File>();
		}
		storage temp;
		Iterator<storage> iterator = group.iterator();
		while(iterator.hasNext())
		{
			temp = iterator.next();
			if(temp.getIp().equals(ip))
			{
				this.mixer.addFile(this.Decrypt(inFile));
				this.tobeMixed.add(inFile);
			}
		}
		if(this.tobeMixed.size() == this.group.size())
		{
			System.out.println("Mixing Start");
			this.mixer.mix();
			this.saveFile = this.mixer.getOutputFile();
			this.GUI.addFile(this.saveFile);
            this.GUI.setSelectedFile(this.saveFile);
            JOptionPane.showMessageDialog(new JEditorPane(), "Congrats, The audio has been recieved and mixed :-)");
            System.out.println("Mixing Finished");
		}
		
	}
	
	public void testMix(Key k, String ip)
	{
		if(ip.equals(this.coordip))
		{
			this.encryptKey = k;
			this.sender.sendFile(Encrypt((File) this.GUI.getSelectedFile()),this.coordip);
			System.out.println("TestMix Recieved from " + ip);
		}
	}
	
	
	/*
	 * Decrypt the file
	 */
	
	private File Decrypt(File inFile)
	{
		System.out.println("Decrypting File" + inFile.getPath());
		Decryptfile sec2=new Decryptfile();
		sec2.setfname("d" + inFile.getPath());
		sec2.setpath(inFile.getPath());
		try {
			sec2.Decrypt(this.encryptKey);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return new File(sec2.getdfilename());
	}
	
	
	private File Encrypt(File inFile)
	{
		System.out.println("Encrypting File" + inFile.getPath());
		Encryptfile sec1 = new Encryptfile();
		sec1.setFileName(inFile.getPath());
		try {
			sec1.encrypt(this.encryptKey);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return new File(sec1.getfilename());
	}
	
	
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		String serverip;
		if(args.length == 0)
		{
			System.out.println("no args");
			serverip = null;
		}
		else
		{
			System.out.println("Server : " + args[0]);
			serverip = args[0];
		}
		// TODO Auto-generated method stub
		new MainController(serverip);
	}


}
